Problem:
Przeprowadzasz audyt istniejącego rozwiązania zbudowanego w oparciu o Databricks i Sparka.
Dla potrzeb budowy nowego modułu potrzebujesz sprawdzić, gdzie w widokach jest odwołanie do tabeli, którą będziesz zmieniał. Masz nieodparte wrażenie, że development został wykonany w sposób niechlujny i w notebookach nie ma wszystkiego. Część została zrobiona i potem zapomniana.
Potrzebujesz wydobyć definicję tabel i widoków zapisaną w hive metastore.
Rozwiązanie:
Użyj polecenia
SHOW CREATE TABLE
Generuje ono skrypt SQL z definicją tabeli lub widoku.
Potrzeba trochę ulepszyć tą metodę. Ona zwraca definicję dla jednej tabeli i widoku. W naszej bazie jest tych tabel i widoków 100+.
Wykorzystamy SHOW VIEWS i SHOW TABLE i zautomatyzujemy sobie pracę. Na koniec zapiszemy wszystko do tabeli.
Definiowanie zmiennych i import modułów
Zacznijmy od zdefiniowania parametrów i przypiszemy im domyślne wartości. Definiowanie parametrów to dla mnie dobra praktyka. Dzięki temu łatwiej przeniesiesz notebook na inne środowisko.
dbutils.widgets.text('db_name','next_level_dm')
dbutils.widgets.text('user_table_def_name','dbx_user_tab_def')
Będziemy jeszcze potrzebowali zaimportować moduły z pythona:
from pyspark.sql.functions import lit
from datetime import datetime
Pierwszy będzie używany do wstawienia kolumny do data frame, drugi do wygenerowania bierzącej daty.
Pobieranie definicji tabel i widoków
Spark posiada dwie możliwości na porbranie definicji widoków:
- Show views
- Show tables
Show views wybiera tylko widoki, natomiast show tables wybiera tabele i widoki. Dlatego, wybierzemy polecenie show view. Dzięki temu dostaniemy tylko widoki. Potem wstawimy do tabeli z metadanymi informację jaki to typ obiektu
def user_view_definitions(db_name: str):
df_views = spark.sql(f"show views in {db_name}" )
user_view_def = None
for row in df_views.collect():
df_view = spark.sql(f"show create table {db_name}.{row.viewName}" )
df_view = df_view.withColumn('database_name',lit(db_name)).withColumn('table_name',lit(row.viewName)).withColumn('type',lit('view'))
user_view_def = df_view.alias('user_view_def') if user_view_def is None else user_view_def.unionAll(df_view)
return user_view_def
Teraz chcemy do definicji widoków dodać jeszcze definicję tabel. Chcemy również odfiltrować definicję widoków, które mamy już w data frame. Dlatego jako jeden z parametrów do fukncji podamy listę widoków, których definicję już posiadamy.
def user_tab_definitions(db_name: str, view_list: []):
df_tables = spark.sql(f"show tables in {db_name}")
user_tab_def = None
for row in df_tables.collect():
#filter out views
if row.tableName not in view_list:
df_table = spark.sql(f"show create table {db_name}.{row.tableName}")
df_table = df_table.withColumn('database_name',lit(db_name)).withColumn('table_name',lit(row.tableName)).withColumn('type',lit('table'))
user_tab_def = df_table.alias('user_tab_def') if user_tab_def is None else user_tab_def.unionAll(df_table)
return user_tab_def
Zostało scalić dane razem i zapisać do tabeli.
O czym warto pamiętać
Show tables - zwraca zarówno definicję widoków jak i tabel. W show tables nie możesz łatwo rozróżnić co jest widokiem a co tabelą, dlatego z pomocą przychodzi show view zawierające tylko definicję widoków.
Definicja widoków jest typu string, dlatego łatwo ją przeszukiwać.
Problem z pierwszego paragrafu jest zaadresowany ale jeszcze nie rozwiązany. Co jeszcze, można zrobić, żeby sprawdzić czy w notebookach jest wszystko co potrzeba i nie brakuje żadnego obiektu?
Na przykład można stworzyć nową bazę i tam zrobić deployment. Ewentualnie, ale bardziej ryzykownie możesz utworzyć sobie definicję widoków i tabel a potem usunąć wszystkie tabele i widoki, dla których stworzyłeś definicję. Dlaczego to ryzykowne? Ponieważ tracisz przy tym wszystkie dane. Zakładając, że pracujesz na środowisku developerskim zapewne możesz to zrobić ale pamiętaj o komunikacji z zespołem. Benefitem będzie uzyskanie większej pewności do kodu, którzy przyszło Ci wspierać.